#!/usr/bin/env python
# Mirros all data from cmatic over to scoreboard
import urllib
import json
from datetime import time
import math
import paramiko
import sys
from auth import SSH_INFOS

GET_URL = 'http://192.168.1.136/cmat18/server/api/get.php'

def get(filetype):
  filename = '%s.json' % filetype
  with open(filename) as f:
    return json.loads(f.read())

def setup_rings():
  # We've always had 8 rings, so might as well hardcode that in :P
  rings = []
  for i in range(0,9):
    rings.append({
      'model': 'boardv1.ring',
      'pk': i,
      'fields': {
        'number': i
      }
    })
  return rings

def setup_config():
  """Sets up level, age groups, genders, event types"""
  print 'Getting basic data...'
  # Fetch data
  levels = get('levels')
  age_groups = get('age_groups')
  genders = get('genders')
  event_types = get('event_types')

  # Rename data to match our convention
  levels_out = [{'model': 'boardv1.level', 'pk': ord(level['chr']), 'fields': {
      'code': level['chr'],
      'name': level['str'],}} for level in levels]
  age_groups_out = [{'model': 'boardv1.agegroup', 'pk': ord(age_group['chr']), 'fields': {
      'code': age_group['chr'],
      'name': age_group['str'],}} for age_group in age_groups]
  genders_out = [{'model': 'boardv1.gender', 'pk': ord(gender['chr']), 'fields': {
      'code': gender['chr'],
      'name': gender['str'],}} for gender in genders]
  event_types_out = [{'model': 'boardv1.eventtype', 'pk': int(event_type['num']), 'fields': {
      'code': event_type['num'],
      'name': event_type['str'],}} for event_type in event_types]

  return (levels_out, age_groups_out, genders_out, event_types_out)

def setup_events():
  """Sets up the cross products of all events"""
  print 'Getting events...'
  # Fetch data
  events = get('events')

  events_dict = dict(
      [('%s%s%s%s' % (event['level_chr'], event['gender_chr'],
       event['age_group_chr'], event['event_type_num']), event) for
      event in events])

  # Rename to match convention
  events_out = [{
    'model': 'boardv1.event',
    'pk': '%s%s%s%s' % (event['level_chr'], event['gender_chr'], event['age_group_chr'], event['event_type_num']),
    'fields': {
      'ring': int(event['ring_id']),
      'event_type': int(event['event_type_num']),
      'order': int(event['ring_order']),
      'level': ord(event['level_chr']),
      'age_group': ord(event['age_group_chr']),
      'gender': ord(event['gender_chr']),
      'shortcode': event['level_chr'] + event['gender_chr'] + event['age_group_chr'] + event['event_type_num'],
      'num_competitors': int(event['num_competitors']),
      'completed': event['end_timestamp'] is not None}} for event in events]

  return events_out, events_dict

def setup_competitors():
  """Imports all competitors"""
  def find_in_competitors(competitors_list, id):
    for competitor in competitors_list:
      if competitor['pk'] == id:
        return competitor
    return None
  print 'Getting competitors... ',

  competitors = get('competitors')
  print '(%d records)' % len(competitors)

  # Cache the placings data by competitor.
  competitor_events = {}
  for scoring in get('scores'):
    comp_id = int(scoring['competitor_id'])
    scoring_id = int('%s%s' % (scoring['event_type_num'], scoring['competitor_id']))
    if comp_id not in scoring:
      competitor_events[comp_id] = []
    competitor_events[comp_id].append(scoring_id)

  # Convert to dict format
  competitors_dict = {}
  for competitor in competitors:
    name_tuple = (competitor['first_name'].lower().strip(),
                  competitor['last_name'].lower().strip())
    comp_id = int(competitor['id'])
    if name_tuple in competitors_dict:
      # Just update the events and note the new ID.
      competitors_dict[name_tuple]['events'].extend(competitor_events[comp_id])
      competitors_dict[name_tuple]['ids'].append(comp_id)
    else:
      competitors_dict[name_tuple] = competitor
      competitors_dict[name_tuple]['events'] = competitor_events[comp_id]
      competitors_dict[name_tuple]['ids'] = [comp_id, ]

  competitors_out = []
  competitors_set = []
  competitors_duplicates = {}  # Find the duplicates.
  competitors_dict_by_id = {}
  for name_tuple, comp in competitors_dict.iteritems():
    competitors_out.append({
      'model': 'boardv1.competitor',
      'pk': min(comp['ids']),  # The lowest ID is the canonical one.
      'fields': {
        'first_name': comp['first_name'].strip(),
        'last_name': comp['last_name'].strip(),
        'level': ord(comp['level_chr']),
        'age_group': ord(comp['age_group_chr']),
        'gender': ord(comp['gender_chr'])
      }})
    for comp_id in comp['ids']:
      # Map all larger comp_id to the smaller one.
      competitors_duplicates[comp_id] = min(comp['ids'])
    competitors_set.append(min(comp['ids']))
    competitors_dict_by_id[min(comp['ids'])] = comp
  return competitors_out, competitors_set, competitors_duplicates, competitors_dict_by_id

def update_placings(competitors_set, competitors_duplicates, competitors_dict, events):
  """Update placings"""
  print 'Getting placings....',
  scores = get('scores')

  placings_out = []
  competitor_events = {}
  for score in scores:
    org_comp_id = int(score['competitor_id'])
    if org_comp_id not in competitors_set:
      # Competitor ID doesn't exist, this could either be an error (In which case
      # we ignore this score set) or a duplicate (we update score['id'] with the
      # correct id)
      if org_comp_id in competitors_duplicates:
        score['competitor_id'] = competitors_duplicates[org_comp_id]
      else:
        print 'Competitor does not exist for %s' % score
    comp_id = int(score['competitor_id'])
    competitor = competitors_dict[int(score['competitor_id'])]
    event = '%s%s%s%s' % (score['level_chr'],
                          score['gender_chr'],
                          score['age_group_chr'],
                          score['event_type_num'])
    placing = {
        'model': 'boardv1.placing',
        'pk': int('%s%d' % (score['event_type_num'], org_comp_id)),
        'fields': {
          'competitor': int(score['competitor_id']),
          'event': event,
          'place': int(score['place']) if score['place'] else 0,
          'final_score': float(score['final_score']) if score['final_score'] else 0.0,
          'time': int(float(score['time'])) if score['time'] else 0,
          'time_deduct': int(float(score['deduct1']) * 100 if score['deduct1'] else 0),
          'other_deduct': int(float(score['deduct2']) * 100 if score['deduct2'] else 0),
          'order': int(score['competitor_order'])
        }
    }
    if comp_id not in competitor_events:
      competitor_events[comp_id] = []
    competitor_events[comp_id].append(event)
    if placing['fields']['event'] not in events:
      print 'Removed %s' % placing['fields']
      continue
    placing['fields']['scores'] = ','.join([
        str(int(float(score['score_1']) * 100) if score['score_1'] else 0),
        str(int(float(score['score_2']) * 100) if score['score_2'] else 0),
        str(int(float(score['score_3']) * 100) if score['score_3'] else 0),
        str(int(float(score['score_4']) * 100) if score['score_4'] else 0),
        str(int(float(score['score_5']) * 100) if score['score_5'] else 0),
        str(int(float(score['score_6']) * 100) if score['score_6'] else 0),
    ])
    placings_out.append(placing)
  print '(%d records)' % len(placings_out)
  return placings_out, competitor_events


def update_competitors_with_events(competitors, competitor_events):
  for comp in competitors:
    comp_id = comp['pk']
    if comp_id in competitor_events:
      comp['fields']['events'] = competitor_events[comp_id]
    else:
      print 'Competitor %d has no events' % comp_id
      comp['fields']['events'] = []
  return competitors


def initial_setup():
  # Mirror all data
  rings = setup_rings()
  levels, age_groups, genders, event_types = setup_config()
  events, events_dict = setup_events()
  competitors, competitors_set, competitors_duplicates, competitors_dict = setup_competitors()
  placings, competitor_events = update_placings(competitors_set,
                                                competitors_duplicates,
                                                competitors_dict,
                                                events_dict)
  competitors = update_competitors_with_events(competitors, competitor_events)
  result = []
  result.extend(rings)
  result.extend(levels)
  result.extend(age_groups)
  result.extend(genders)
  result.extend(event_types)
  result.extend(events)
  result.extend(competitors)
  result.extend(placings)
  return result

# Only do placings
def just_update():
  # We shifted the competitors ID a little, this is just data we need to reconstruct it
  with open('competitors_set.json') as f:
    competitors_set = set(json.loads(f.read()))
  with open('competitors_duplicatess.json') as f:
    competitors_duplicatess = json.loads(f.read())
  return update_placings(competitors_set, competitors_duplicatess)

def run_remotely(ssh, command):
  print
  print "===Calling %s===" % command
  stdin, stdout, stderr = ssh.exec_command(command)
  while True:
    out = stdout.read()
    err = stderr.read()
    if not out and not err:
      break
    sys.stdout.write(out)
    sys.stderr.write(err)
  print "===Done calling %s===" % command
  return stdout.channel.recv_exit_status()


if __name__ == '__main__':
  if len(sys.argv) > 1 and sys.argv[1] == 'update':
    result = just_update()
  else:
    result = initial_setup()
  with open('database.json', 'w') as f:
    f.write(json.dumps(result))
  print "uploading database.json via SFTP"
  success = True
  for ssh_info in SSH_INFOS:
    HOST = ssh_info['HOST']
    PORT = ssh_info['PORT']
    USERNAME = ssh_info['USERNAME']
    PASSWORD = ssh_info['PASSWORD'] if 'PASSWORD' in ssh_info else None
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    print "Connecting to %s..." % HOST
    ssh.connect(HOST, PORT, USERNAME, PASSWORD)
    sftp = ssh.open_sftp()
    print "Uploading..."
    attr = sftp.put('./database.json', '/home/%s/scoreboard/database.json' % USERNAME,)
    return_code = run_remotely(ssh, 'cd /home/%s/scoreboard; '
        'export PYTHONPATH=$PYTHONPATH:$HOME/local/lib/python2.6/site-packages; '
        'python ./manage.py loaddata ./database.json; touch ./updated.txt' % USERNAME)
    if return_code == 0:
      print "Success!!!"
    else:
      success = False
    sftp.close()
    ssh.close()
  print
  if success:
    print "All were successful"
  else:
    print "There were errors somewhere"